"""
@author: 江同学呀
@file: search_api.py
@date: 2024/7/25 11:33
@desc:
    https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-search.html
"""

from typing import Optional, List, Type, Dict, Union, Iterable, Literal

from elastic_transport import ObjectApiResponse
from elasticsearch import Elasticsearch

from espc.common.common import Number
from espc.core.core import core
from espc.orm.api.base_api import BaseApi
from espc.orm.model.aggregation.aggregation import Aggregation
from espc.orm.model.dsl.collapse_search_results import Collapse
from espc.orm.model.dsl.paginate_search_results import Pit, Slice
from espc.orm.model.dsl.search_fields import Fields, Source, ScriptFields
from espc.orm.model.dsl.queries.base_queries import _BaseQueries
from espc.orm.model.dsl.filter_search_results import Rescorer
from espc.orm.model.dsl.search_multiple_indices import IndicesBoost
from espc.orm.model.mapping.field.base_field.base_field import _BaseField
from espc.orm.model.mapping.field.base_field.base_numeric_field import _BaseNumericField
from espc.orm.model.mapping.field.common.keyword_field import KeywordField
from espc.orm.model.model.base_alias import BaseAlias
from espc.orm.model.model.base_index import BaseIndex
from espc.orm.model.dsl.sort_search_results import Sort
from espc.orm.model.dsl.highlighting import Highlight
from espc.orm.model.text_analysis.analyzer import BaseAnalyzer


class SearchApi(BaseApi):
    """
    查询
    """

    def __init__(
            self,
            index: Union[str, Type[BaseIndex], Type[BaseAlias], List[Type[BaseIndex]], List[Type[BaseAlias]]],
            *args, **kwargs
    ):
        # todo
        super().__init__(*args, **kwargs)
        # if index not in ESPrivyCouncil._core._index_dict:
        #     raise ESPCIndexUninitError(f"未初始化{index}")
        self._body: Dict[str, Union[str, List, Dict, Number]] = {}

        self._index: Union[str, Type[BaseIndex], Type[BaseAlias], List[Type[BaseIndex]], List[Type[BaseAlias]]] = index

        self._query: Optional[List[_BaseQueries]] = None
        self._aggs: Optional[Dict[str, Aggregation]] = None

        self._collapse: Union[KeywordField, _BaseNumericField, Collapse, Dict, None] = None
        self._post_filter: Optional[List[_BaseQueries]] = None

        self._rescorer: Optional[List[Union[Dict, Rescorer]]] = None

        self._highlight: Union[Highlight, Dict, None] = None

        self._from: Optional[int] = None
        self._size: Optional[int] = None
        self._pit: Union[Dict, Pit, None] = None
        self._search_after: Optional[List[Union[str, Number]]] = None
        self._track_total_hits: Optional[bool] = None
        self._slice: Union[Dict, Slice, None] = None

        self._source: Union[Iterable[Union[_BaseField, str]], Source, Dict, bool, None] = None
        self._fields: Optional[List[Union[_BaseField, str, Fields, Dict]]] = None
        self._docvalue_fields: Optional[List[Union[_BaseField, str, Fields, Dict]]] = None
        self._stored_fields: Union[Iterable[Union[_BaseField, str]], str, None] = None
        self._script_fields: Union[Dict, ScriptFields, None] = None

        self._indices_boost: Optional[List[Union[IndicesBoost, Dict]]] = None

        self._sort: Optional[List[Union[_BaseField, Sort, str, Dict]]] = None
        self._track_scores: Optional[bool] = None

        self._min_score: Optional[Number] = None
        self._runtime_mappings: Union[
            Dict[str, Dict[Literal["type", "script"], str]], Iterable[_BaseField], None
        ] = None
        return

    def query(self, *query: _BaseQueries) -> "SearchApi":
        """
        添加查询条件
        :param query:
        :return:
        """
        if self._query is None:
            self._query: List[_BaseQueries] = []
        self._query.extend(query)
        return self

    def aggs(self, *args) -> "SearchApi":
        """
        添加聚合条件
        :param args:
        :return:
        """
        if self._aggs is None:
            self._aggs: List[Aggregation] = []
        self._aggs.extend(args)
        return self

    def collapse(self, collapse: Union[KeywordField, _BaseNumericField, Collapse, Dict, Collapse, None]) -> "SearchApi":
        """
        设置去重字段
        https://www.elastic.co/guide/en/elasticsearch/reference/7.17/collapse-search-results.html
        :param collapse:
        :return:
        """
        self._collapse: Union[KeywordField, _BaseNumericField, Collapse, Dict] = collapse
        return self

    def post_filter(self, *query: _BaseQueries) -> "SearchApi":
        """
        设置后过滤器
        https://www.elastic.co/guide/en/elasticsearch/reference/7.17/filter-search-results.html#post-filter

        When you use the post_filter parameter to filter search results, the search hits are filtered after the
        aggregations are calculated. A post filter has no impact on the aggregation results.
        当您使用 post_filter 参数筛选搜索结果时，将在计算聚合后筛选搜索命中数。后过滤器对聚合结果没有影响。

        :param query:
        :return:
        """
        if self._post_filter is None:
            self._post_filter: List[_BaseQueries] = []
        self._post_filter.extend(query)
        return self

    def rescore(self, *rescorer: Union[Dict, Rescorer]) -> "SearchApi":
        """
        对筛选的搜索结果进行重新评分
        https://www.elastic.co/guide/en/elasticsearch/reference/7.17/filter-search-results.html#rescore

        Rescoring can help to improve precision by reordering just the top (eg 100 - 500) documents returned by the
        query and post_filter phases, using a secondary (usually more costly) algorithm, instead of applying the costly
        algorithm to all documents in the index.
        A rescore request is executed on each shard before it returns its results to be sorted by the node handling the
        overall search request.
        Currently the rescore API has only one implementation: the query rescorer, which uses a query to tweak the
        scoring. In the future, alternative rescorers may be made available, for example, a pair-wise rescorer.

        重新评分可以通过使用辅助（通常更昂贵）算法（而不是将昂贵的算法应用于索引中的所有文档）仅对 query 和 post_filter 阶段返回的前 100 - 500 个）文档重新排序来帮助提高精度。
        在每个分片上执行重新评分请求，然后返回其结果，以便由处理整个搜索请求的节点进行排序。
        目前 rescore API 只有一个实现：query rescorer，它使用查询来调整评分。将来，可能会提供替代的 rescorer，例如，成对的 rescorer。
        :return:
        """
        if self._rescorer is None:
            self._rescorer: List[Rescorer] = []
        self._rescorer.extend(rescorer)
        return self

    def highlight(self, highlight: Union[Highlight, Dict]) -> "SearchApi":
        """
        设置高亮
        :param highlight:
        :return:
        """
        self._highlight: Union[Highlight, Dict] = highlight
        return self

    def from_(self, from_: int) -> "SearchApi":
        """
        设置起始位置
        :param from_:
        :return:
        """
        self._from: int = from_
        return self

    def size(self, size: int) -> "SearchApi":
        """
        设置返回数量
        :param size:
        :return:
        """
        self._size: int = size
        return self

    def pit(self, pit: Union[Dict, Pit]) -> "SearchApi":
        """
        设置持久化查询
        :param pit:
        :return:
        """
        self._pit: Union[Dict, Pit] = pit
        return self

    def search_after(self, search_after: List[Union[str, Number]]) -> "SearchApi":
        """
        设置搜索后
        :param search_after:
        :return:
        """
        self._search_after: List[Union[str, Number]] = search_after
        return self

    def track_total_hits(self, track_total_hits: bool) -> "SearchApi":
        """

        :param track_total_hits:
        :return:
        """
        self._track_total_hits: bool = track_total_hits
        return self

    def slice(self, slice_: Union[Dict, Slice]) -> "SearchApi":
        """
        设置分片
        :param slice_:
        :return:
        """
        self._slice: Union[Dict, Slice] = slice_
        return self

    def source(
            self, fields: Union[_BaseField, str, Iterable[Union[_BaseField, str]], Source, Dict, bool],
            *args: Union[_BaseField, str]
    ) -> "SearchApi":
        """
        设置返回字段
        :param fields:
        :return:
        """
        if not args:
            self._source: Union[Iterable[Union[_BaseField, str]], Source, Dict, bool] = fields
        elif isinstance(fields, Iterable):
            self._source: Union[Iterable[Union[_BaseField, str]], Source, Dict, bool] = list(fields) + list(args)
        elif isinstance(fields, (_BaseField, str)):
            self._source: Union[Iterable[Union[_BaseField, str]], Source, Dict, bool] = [fields] + list(args)
        else:
            self._source: Union[Iterable[Union[_BaseField, str]], Source, Dict, bool] = fields
        return self

    def fields(self, *fields: Union[_BaseField, str, Fields, Dict]) -> "SearchApi":
        """
        设置字段
        :param fields:
        :return:
        """
        if not self._fields:
            self._fields: List[Union[_BaseField, str, Fields, Dict]] = []
        self._fields.extend(fields)
        return self

    def docvalue_fields(self, *docvalue_fields: Union[_BaseField, str, Fields, Dict]) -> "SearchApi":
        """
        设置文档值字段
        :param docvalue_fields:
        :return:
        """
        self._docvalue_fields: Iterable[Union[_BaseField, str, Fields, Dict]] = docvalue_fields
        return self

    def stored_fields(self, stored_fields: Union[Iterable[Union[_BaseField, str]], str]) -> "SearchApi":
        """
        设置存储字段
        :param stored_fields:
        :return:
        """
        self._stored_fields: Union[Iterable[Union[_BaseField, str]], str] = stored_fields
        return self

    def script_fields(self, script_fields: Union[Dict, ScriptFields]) -> "SearchApi":
        """
        设置脚本字段
        :param script_fields:
        :return:
        """
        self._script_fields: Union[Dict, ScriptFields] = script_fields
        return self

    def indices_boost(self, *indices_boost: Union[IndicesBoost, Dict]) -> "SearchApi":
        """
        设置索引Boost
        :param indices_boost:
        :return:
        """
        if not self._indices_boost:
            self._indices_boost: List[Union[IndicesBoost, Dict]] = []
        self._indices_boost.extend(indices_boost)
        return self

    def sort(self, *sort: Union[_BaseField, Sort, str]) -> "SearchApi":
        """
        设置排序
        :param sort:
        :return:
        """
        if not self._sort:
            self._sort: List[Union[_BaseField, Sort, str]] = []
        self._sort.extend(sort)
        return self

    def track_scores(self, track_scores: bool) -> "SearchApi":
        """
        设置跟踪分数
        :param track_scores:
        :return:
        """
        self._track_scores: bool = track_scores
        return self

    def min_score(self, min_score: Number) -> "SearchApi":
        """
        设置最小分数
        :param min_score:
        :return:
        """
        self._min_score: Number = min_score
        return self

    def runtime_mappings(
            self, runtime_mappings: Union[Dict[str, Dict[Literal["type", "script"], str]], Iterable[_BaseField]]
    ) -> "SearchApi":
        """
        设置运行时映射

        (Optional, object of objects) Defines one or more runtime fields in the search request. These fields take
        precedence over mapped fields with the same name.
        Properties of runtime_mappings objects
            <field-name>
                (Required, object) Configuration for the runtime field. The key is the field name.
                Properties of <field-name>
                    type
                        (Required, string) Field type, which can be any of the following:
                            - boolean
                            - composite
                            - date
                            - double
                            - geo_point
                            - ip
                            - keyword
                            - long
                    script
                        (Optional, string) Painless script executed at query time. The script has access to the entire
                        context of a document, including the original _source and any mapped fields plus their values.
                        This script must include emit to return calculated values.

        （可选，对象的对象）在搜索请求中定义一个或多个运行时字段。这些字段优先于具有相同名称的映射字段。
        runtime_mappings 对象的属性
            <field-name>
                （必需，对象）runtime 字段的配置。键是字段名称。
                < 的属性field-name>
                    type
                        （必需，字符串）Field type （字段类型），可以是以下任意值：

                            - boolean
                            - composite
                            - date
                            - double
                            - geo_point
                            - ip
                            - keyword
                            - long
                    script
                        （可选，字符串）在查询时执行的无痛脚本。该脚本可以访问文档的整个上下文，包括原始_source和任何映射的字段及其值。
                        此脚本必须包含 emit 才能返回计算值。
        :param runtime_mappings:
        :return:
        """
        self._runtime_mappings: Union[Dict, Iterable[_BaseField]] = (
            runtime_mappings
            if isinstance(runtime_mappings, Dict)
            else {field._field_name: field._build() for field in runtime_mappings}
        )
        return self

    def _build(self) -> Dict:
        _body: Dict = {}
        if self._query:
            _body["query"] = {query.type: query._build() for query in self._query}
        if self._aggs:
            _body["aggs"] = {name: {agg.type: agg._build()} for name, agg in self._aggs.items()}
        if self._collapse:
            _body["collapse"] = (
                self._collapse._build()
                if isinstance(self._collapse, Collapse)
                else self._collapse
            )
        if self._post_filter:
            _body["post_filter"] = [post_filter._build() for post_filter in self._post_filter]
        if self._rescorer:
            _body["rescore"] = [
                rescorer._build() if isinstance(rescorer, Rescorer) else rescorer for rescorer in self._rescorer
            ]
        if self._highlight:
            _body["highlight"] = self._highlight._build() if isinstance(self._highlight, Highlight) else self._highlight
        if self._from is not None:
            _body["from"] = self._from
        if self._size is not None:
            _body["size"] = self._size
        if self._pit:
            _body["pit"] = self._pit._build() if isinstance(self._pit, Pit) else self._pit
        if self._search_after:
            _body["search_after"] = self._search_after
        if self._track_total_hits is not None:
            _body["track_total_hits"] = self._track_total_hits
        if self._slice:
            _body["slice"] = self._slice._build() if isinstance(self._slice, Slice) else self._slice
        if self._source:
            _body["_source"] = (
                self._source._build()
                if isinstance(self._source, Source)
                else (
                    self._source
                    if isinstance(self._source, (Dict, bool)) else (
                        [s if isinstance(s, str) else s._field_name for s in self._source]
                        if isinstance(self._source, Iterable) else (
                            self._source.field_name if isinstance(self._source, _BaseField) else self._source
                        )
                    )
                )
            )
        if self._fields:
            _body["fields"] = (
                f._build()
                if isinstance(f, Fields)
                else (
                    f if isinstance(f, (str, Dict)) else f._field_name
                )
                for f in self._fields
            )
        if self._docvalue_fields:
            _body["docvalue_fields"] = (
                docvalue_field._build()
                if isinstance(docvalue_field, Fields)
                else (
                    docvalue_field
                    if isinstance(docvalue_field, (str, Dict))
                    else docvalue_field._field_name
                )
                for docvalue_field in self._docvalue_fields
            )
        if self._stored_fields:
            _body["stored_fields"] = (
                self._stored_fields
                if isinstance(self._stored_fields, str)
                else (
                    stored_field._build()
                    if isinstance(stored_field, Fields)
                    else (
                        stored_field
                        if isinstance(stored_field, (str, Dict))
                        else stored_field._field_name
                    )
                    for stored_field in self._stored_fields
                )
            )
        if self._script_fields:
            _body["script_fields"] = (
                self._script_fields._build()
                if isinstance(self._script_fields, ScriptFields)
                else self._script_fields
            )
        if self._indices_boost:
            _body["indices_boost"] = [
                indices_boost._build() if isinstance(indices_boost, IndicesBoost) else indices_boost
                for indices_boost in self._indices_boost
            ]
        if self._sort:
            _body["sort"] = (
                s._build()
                if isinstance(s, Sort)
                else s if isinstance(s, (str, Dict)) else s._field_name
                for s in self._sort
            )
        if self._track_scores is not None:
            _body["track_scores"] = self._track_scores
        if self._min_score is not None:
            _body["min_score"] = self._min_score
        if self._runtime_mappings:
            _body["runtime_mappings"] = (
                self._runtime_mappings
                if isinstance(self._runtime_mappings, Dict)
                else {rm._field_type: rm._build() for rm in self._runtime_mappings}
            )
        return _body

    def post(
            self, human: bool = None, filter_path: Union[str, Iterable[str]] = None,
            # flat_settings: bool = None,
            error_trace: bool = None, allow_no_indices: bool = None, allow_partial_search_results: bool = None,
            analyzer: Union[str, BaseAnalyzer] = None, analyze_wildcard: bool = None, batched_reduce_size: int = None,
            ccs_minimize_roundtrips: bool = None, default_operator: Literal["AND", "OR"] = None, df: str = None,
            docvalue_fields: str = None, expand_wildcards: Literal["all", "open", "closed", "hidden", "none"] = None,
            # from_: int = None,
            ignore_throttled: bool = None, ignore_unavailable: bool = None, lenient: bool = None,
            max_concurrent_shard_requests: int = None, pre_filter_shard_size: int = None, preference: str = None,
            q: str = None, request_cache: bool = None, rest_total_hits_as_int: bool = None, routing: str = None,
            scroll: str = None, search_type: Literal["query_then_fetch", "dfs_query_then_fetch"] = None,
            seq_no_primary_term: bool = None,
            # size: int = None,
            sort: Union[str, Iterable[str]] = None,
            source: Union[bool, str, Iterable[str]] = None, source_excludes: Union[str, Iterable[str]] = None,
            source_includes: Union[str, Iterable[str]] = None, stats: str = None, suggest_size: int = None,
            stored_fields: Union[str, Iterable[str]] = None, suggest_field: Union[str, Iterable[str]] = None,
            suggest_mode: Literal["missing", "popular", "always"] = None, suggest_text: str = None, timeout: str = None,
            terminate_after: int = None, track_scores: bool = None, track_total_hits: Union[bool, int] = None,
            typed_keys: bool = None, version: bool = None, *args, **kwargs
    ) -> Dict:
        """
        POST请求
        :param human:
            Statistics are returned in a format suitable for humans (e.g. "exists_time": "1h" or "size": "1kb") and for
            computers (e.g. "exists_time_in_millis": 3600000 or "size_in_bytes": 1024). The human readable values can be
            turned off by adding ?human=false to the query string. This makes sense when the stats results are being
            consumed by a monitoring tool, rather than intended for human consumption. The default for the human flag is
            false.
            统计数据以适合人类的格式返回（例如“exists_time”： “1h”或“size”： “1kb”）和计算机（例如“exists_time_in_millis”： 3600000
            或 “size_in_bytes”： 1024）。可以通过将 ？human=false 添加到查询字符串来关闭人类可读值。当统计结果由监控工具使用而不是供人类
            使用时，这是有意义的。人为标志的默认值为 false。
        :param filter_path:
            All REST APIs accept a filter_path parameter that can be used to reduce the response returned by
            Elasticsearch. This parameter takes a comma separated list of filters expressed with the dot notation.
            所有 REST API 都接受 filter_path 参数，该参数可用于减少 Elasticsearch 返回的响应。此参数采用逗号分隔的过滤器列表，以点表示
            法表示
        # :param flat_settings:
        #     The flat_settings flag affects rendering of the lists of settings. When the flat_settings flag is true,
        #     settings are returned in a flat format.
        #     flat_settings 标志会影响设置列表的呈现。当 flat_settings 标志为 true 时，设置以平面格式返回
        :param error_trace:
            By default when a request returns an error Elasticsearch doesn’t include the stack trace of the error. You
            can enable that behavior by setting the error_trace url parameter to true.
            默认情况下，当请求返回错误时，Elasticsearch 不包含错误的堆栈跟踪。您可以通过将 error_trace url 参数设置为 true 来启用该行为。
        :param allow_no_indices:
            (Optional, Boolean) If false, the request returns an error if any wildcard expression, index alias, or _all
            value targets only missing or closed indices. This behavior applies even if the request targets other open
            indices. For example, a request targeting foo*,bar* returns an error if an index starts with foo but no
            index starts with bar.
            Defaults to true.

            （可选，布尔值）如果为 false，则当任何通配符表达式、索引别名或_all值仅针对缺失或已关闭的索引时，请求将返回错误。即使请求以其他开放
            索引为目标，此行为也适用。例如，如果索引以 foo 开头，但没有索引以 bar 开头，则以 foo*，bar* 为目标的请求将返回错误。
            默认为 true。
        :param allow_partial_search_results:
            (Optional, Boolean) If true, returns partial results if there are shard request timeouts or shard failures.
            If false, returns an error with no partial results. Defaults to true.
            To override the default for this field, set the search.default_allow_partial_results cluster setting to false.

            （可选，布尔值）如果为 true，则在存在分片请求超时或分片失败时返回部分结果。如果为 false，则返回没有部分结果的错误。默认为 true。
            要覆盖此字段的默认值，请将 search.default_allow_partial_results cluster （ 集群） 设置为 false。
        :param analyzer:
            (Optional, string) Analyzer to use for the query string.
            This parameter can only be used when the q query string parameter is specified.

            （可选，字符串）用于查询字符串的分析器。
            仅当指定了 q query string 参数时，才能使用此参数。
        :param analyze_wildcard:
            (Optional, Boolean) If true, wildcard and prefix queries are analyzed. Defaults to false.
            This parameter can only be used when the q query string parameter is specified.

            （可选，布尔值）如果为 true，则分析通配符和前缀查询。默认为 false。
            仅当指定了 q query string 参数时，才能使用此参数。
        :param batched_reduce_size:
            (Optional, integer) The number of shard results that should be reduced at once on the coordinating node.
            This value should be used as a protection mechanism to reduce the memory overhead per search request if the
            potential number of shards in the request can be large. Defaults to 512.
            （可选，整数）应在协调节点上立即减少的分片结果数。如果请求中的潜在分片数量可能很大，则应将此值用作保护机制，以减少每个搜索请求的内存
            开销。默认值为 512。
        :param ccs_minimize_roundtrips:
            (Optional, Boolean) If true, network round-trips between the coordinating node and the remote clusters are
            minimized when executing cross-cluster search (CCS) requests. See How cross-cluster search handles network
            delays. Defaults to true.
            （可选，布尔值）如果为 true，则在执行跨集群搜索 （CCS） 请求时，协调节点和远程集群之间的网络往返将最小化。请参阅跨集群搜索如何处
            理网络延迟。默认为 true。
        :param default_operator:
            (Optional, string) The default operator for query string query: AND or OR. Defaults to OR.
            This parameter can only be used when the q query string parameter is specified.

            （可选，字符串）查询字符串查询的默认运算符：AND 或 OR。默认为 OR。
            仅当指定了 q query string 参数时，才能使用此参数。
        :param df:
            (Optional, string) Field to use as default where no field prefix is given in the query string.
            This parameter can only be used when the q query string parameter is specified.

            （可选，字符串）用作默认字段的字段，其中查询字符串中未提供字段前缀。
            仅当指定了 q query string 参数时，才能使用此参数。
        :param docvalue_fields:
            (Optional, string) A comma-separated list of fields to return as the docvalue representation of a field for
            each hit.
            （可选，字符串）以逗号分隔的字段列表，作为每次点击的字段的文档值表示形式返回。
        :param expand_wildcards:
            (Optional, string) Type of index that wildcard patterns can match. If the request can target data streams,
            this argument determines whether wildcard expressions match hidden data streams. Supports comma-separated
            values, such as open,hidden. Valid values are:
                all
                    Match any data stream or index, including hidden ones.
                open
                    Match open, non-hidden indices. Also matches any non-hidden data stream.
                closed
                    Match closed, non-hidden indices. Also matches any non-hidden data stream. Data streams cannot be
                    closed.
                hidden
                    Match hidden data streams and hidden indices. Must be combined with open, closed, or both.
                none
                    Wildcard patterns are not accepted.
            Defaults to open.

            （可选，字符串）通配符模式可以匹配的索引类型。如果请求可以定位数据流，则此参数将确定通配符表达式是否与隐藏的数据流匹配。支持逗号分
            隔值，例如 open，hidden。有效值为：
                all
                    匹配任何数据流或索引，包括隐藏的数据流或索引。
                open
                    匹配打开的非隐藏索引。还匹配任何非隐藏的数据流。
                closed
                    匹配已关闭的非隐藏索引。还匹配任何非隐藏的数据流。无法关闭数据流。
                hidden
                    匹配隐藏的数据流和隐藏的索引。必须与 open 和/或 closed 组合使用。
                none
                    不接受通配符模式。
            默认为 open。
        # :param from_:
        #     (Optional, integer) Starting document offset. Defaults to 0.
        #     By default, you cannot page through more than 10,000 hits using the from and size parameters. To page
        #     through more hits, use the search_after parameter.
        #
        #     （可选，整数）开始文档偏移。默认为 0。
        #     默认情况下，您无法使用 from 和 size 参数分页浏览超过 10,000 个点击。要分页显示更多点击，请使用 search_after 参数。
        :param ignore_throttled:
            (Optional, Boolean) If true, concrete, expanded or aliased indices will be ignored when frozen. Defaults to
            true.
            （可选，布尔值）如果为 true，则冻结时将忽略具体、扩展或别名索引。默认为 true。
        :param ignore_unavailable:
            (Optional, Boolean) If false, the request returns an error if it targets a missing or closed index.
            Defaults to false.
            （可选，布尔值）如果为 false，则请求以缺失或已关闭的索引为目标时返回错误。默认为 false。
        :param lenient:
            (Optional, Boolean) If true, format-based query failures (such as providing text to a numeric field) in the
            query string will be ignored. Defaults to false.
            This parameter can only be used when the q query string parameter is specified.

            （可选，布尔值）如果为 true，则将忽略查询字符串中基于格式的查询失败（例如向数字字段提供文本）。默认为 false。
            仅当指定了 q query string 参数时，才能使用此参数。
        :param max_concurrent_shard_requests:
            (Optional, integer) Defines the number of concurrent shard requests per node this search executes
            concurrently. This value should be used to limit the impact of the search on the cluster in order to limit
            the number of concurrent shard requests. Defaults to 5.
            （可选，整数）定义此搜索并发执行的每个节点的并发分片请求数。此值应用于限制搜索对集群的影响，以限制并发分片请求的数量。默认值为 5。
        :param pre_filter_shard_size:
            (Optional, integer) Defines a threshold that enforces a pre-filter roundtrip to prefilter search shards
            based on query rewriting if the number of shards the search request expands to exceeds the threshold. This
            filter roundtrip can limit the number of shards significantly if for instance a shard can not match any
            documents based on its rewrite method ie. if date filters are mandatory to match but the shard bounds and
            the query are disjoint. When unspecified, the pre-filter phase is executed if any of these conditions is
            met:
                The request targets more than 128 shards.
                The request targets one or more read-only index.
                The primary sort of the query targets an indexed field.

            （可选，整数）定义一个阈值，如果搜索请求扩展到的分片数超过阈值，则根据查询重写强制执行预筛选往返以预筛选搜索分片。此过滤器往返可以显
            着限制分片的数量，例如，如果分片无法根据其 rewrite 方法匹配任何文档，即。如果日期筛选条件必须匹配，但分片边界和查询不相交。如果未
            指定，则如果满足以下任一条件，则执行预筛选阶段：
                该请求以超过 128 个分片为目标。
                该请求以一个或多个只读索引为目标。
                查询的主要排序以索引字段为目标。
        :param preference:
            (Optional, string) Nodes and shards used for the search. By default, Elasticsearch selects from eligible
            nodes and shards using adaptive replica selection, accounting for allocation awareness.
            Valid values for preference
                _only_local
                    Run the search only on shards on the local node.
                _local
                    If possible, run the search on shards on the local node. If not, select shards using the default
                    method.
                _only_nodes:<node-id>,<node-id>
                    Run the search on only the specified nodes IDs. If suitable shards exist on more than one selected
                    node, use shards on those nodes using the default method. If none of the specified nodes are
                    available, select shards from any available node using the default method.
                _prefer_nodes:<node-id>,<node-id>
                    If possible, run the search on the specified nodes IDs. If not, select shards using the default
                    method.
                _shards:<shard>,<shard>
                    Run the search only on the specified shards. You can combine this value with other preference
                    values, excluding <custom-string>. However, the _shards value must come first.
                    For example: _shards:2,3|_local.
                <custom-string>
                    Any string that does not start with _. If the cluster state and selected shards do not change,
                    searches using the same <custom-string> value are routed to the same shards in the same order.

            （可选，字符串）用于搜索的节点和分片。默认情况下，Elasticsearch 使用自适应副本选择从符合条件的节点和分片中进行选择，同时考虑分配
            感知。
            首选项的有效值
                _only_local
                    仅在本地节点上的分片上运行搜索。
                _local
                    如果可能，请在本地节点上的分片上运行搜索。如果没有，请使用默认方法选择分片。
                _only_nodes:<node-id>,<node-id>
                    仅对指定的节点 ID 运行搜索。如果多个选定节点上存在合适的分片，请使用默认方法在这些节点上使用分片。如果指定的节点都不可用，
                    请使用默认方法从任何可用节点中选择分片。
                _prefer_nodes:<node-id>,<node-id>
                    如果可能，请对指定的节点 ID 运行搜索。如果没有，请使用默认方法选择分片。
                _shards:<shard>,<shard>
                    仅对指定的分片运行搜索。您可以将此值与其他首选项值（不包括 <custom-string>）合并。但是，_shards值必须放在第一位。
                    例如：_shards：2,3|_local。
                <custom-string>
                    任何不以 _ 开头的字符串。如果集群状态和所选分片未更改，则使用相同的 <custom-string> 值的搜索将按相同的顺序路由到相同
                    的分片。
        :param q:
            (Optional, string) Query in the Lucene query string syntax.
            You can use the q parameter to run a query parameter search. Query parameter searches do not support the
            full Elasticsearch Query DSL but are handy for testing.

            （可选，字符串）查询。
            您可以使用 q 参数运行查询参数搜索。查询参数搜索不支持完整的 Elasticsearch 查询 DSL，但便于测试。
        :param request_cache:
            (Optional, Boolean) If true, the caching of search results is enabled for requests where size is 0. See
            Shard request cache settings. Defaults to index level settings.
            （可选，布尔值）如果为 true，则为 size 为 0 的请求启用搜索结果缓存。请参阅分片请求缓存设置。默认为索引级别设置。
        :param rest_total_hits_as_int:
            (Optional, Boolean) Indicates whether hits.total should be rendered as an integer or an object in the rest
            search response. Defaults to false.
            （可选，布尔值）指示 hits.total 在 rest 搜索响应中应呈现为整数还是对象。默认为 false。
        :param routing:
            (Optional, string) Custom value used to route operations to a specific shard.
            （可选，字符串）用于将作路由到特定分片的自定义值。
        :param scroll:
            (Optional, time value) Period to retain the search context for scrolling. See Scroll search results.
            By default, this value cannot exceed 1d (24 hours). You can change this limit using the
            search.max_keep_alive cluster-level setting.

            （可选，时间值）Period 保留用于滚动的搜索上下文。请参阅滚动搜索结果。
            默认情况下，此值不能超过 1d（24 小时）。您可以使用 search.max_keep_alive 集群级别设置更改此限制。
        :param search_type:
            (Optional, string) How distributed term frequencies are calculated for relevance scoring.
            Valid values for search_type
                query_then_fetch
                    (Default) Distributed term frequencies are calculated locally for each shard running the search. We
                    recommend this option for faster searches with potentially less accurate scoring.
                dfs_query_then_fetch
                    Distributed term frequencies are calculated globally, using information gathered from all shards
                    running the search. While this option increases the accuracy of scoring, it adds a round-trip to
                    each shard, which can result in slower searches.

            （可选，字符串）如何计算相关性评分的分布式词频。
            search_type 的有效值
                query_then_fetch
                    （默认）分布式字词频率是在本地计算运行搜索的每个分片的。我们建议使用此选项来提高搜索速度，但评分可能不太准确。
                dfs_query_then_fetch
                    分布式词频率是使用从运行搜索的所有分片中收集的信息进行全局计算的。虽然此选项提高了评分的准确性，但它会为每个分片增加往返，
                    这可能会导致搜索速度变慢。
        :param seq_no_primary_term:
            (Optional, Boolean) If true, returns sequence number and primary term of the last modification of each hit.
            See Optimistic concurrency control.
            （可选，布尔值）如果为 true，则返回每个点击的最后修改的序列号和主要术语。请参阅 乐观并发控制。
        # :param size:
        #     (Optional, integer) Defines the number of hits to return. Defaults to 10.
        #     By default, you cannot page through more than 10,000 hits using the from and size parameters. To page
        #     through more hits, use the search_after parameter.
        #
        #     （可选，整数）定义要返回的点击数。默认值为 10。
        #     默认情况下，您无法使用 from 和 size 参数分页浏览超过 10,000 个点击。要分页显示更多点击，请使用 search_after 参数。
        :param sort:
            (Optional, string) A comma-separated list of <field>:<direction> pairs.
            （可选，字符串）以逗号分隔的 ： 对列表。
        :param source:
            (Optional) Indicates which source fields are returned for matching documents. These fields are returned in
            the hits._source property of the search response. Defaults to true.
            Valid values for _source
                true
                    (Boolean) The entire document source is returned.
                false
                    (Boolean) The document source is not returned.
                <string>
                    (string) Comma-separated list of source fields to return. Wildcard (*) patterns are supported.

            （可选）指示为匹配文档返回哪些源字段。这些字段在搜索响应的 hits._source 属性中返回。默认为 true。
            _source 的有效值
                true
                    （布尔值）将返回整个文档源。
                false
                    （布尔值）不返回文档源。
                <string>
                    （字符串）要返回的源字段的逗号分隔列表。支持通配符 （*） 模式。
        :param source_excludes:
            (Optional, string) A comma-separated list of source fields to exclude from the response.
            You can also use this parameter to exclude fields from the subset specified in _source_includes query
            parameter.
            If the _source parameter is false, this parameter is ignored.

            （可选，字符串）要从响应中排除的源字段的逗号分隔列表。
            您还可以使用此参数从 _source_includes query 参数中指定的子集中排除字段。
            如果 _source 参数为 false，则忽略该参数。
        :param source_includes:
            (Optional, string) A comma-separated list of source fields to include in the response.
            If this parameter is specified, only these source fields are returned. You can exclude fields from this
            subset using the _source_excludes query parameter.
            If the _source parameter is false, this parameter is ignored.

            （可选，字符串）要包含在响应中的源字段的逗号分隔列表。
            如果指定了此参数，则仅返回这些源字段。您可以使用 _source_excludes 查询参数从此子集中排除字段。
            如果 _source 参数为 false，则忽略该参数。
        :param stats:
            (Optional, string) Specific tag of the request for logging and statistical purposes.
            （可选，字符串）用于日志记录和统计目的的请求的特定标记。
        :param stored_fields:
            (Optional, string) A comma-separated list of stored fields to return as part of a hit. If no fields are
            specified, no stored fields are included in the response.
            If this field is specified, the _source parameter defaults to false. You can pass _source: true to return
            both source fields and stored fields in the search response.

            （可选，字符串）作为点击的一部分返回的存储字段的逗号分隔列表。如果未指定字段，则响应中不包含任何存储字段。
            如果指定了此字段，则 _source 参数默认为 false。您可以传递 _source： true 以在搜索响应中返回源字段和存储字段。
        :param suggest_field:
            (Optional, string) Specifies which field to use for suggestions.
            （可选，字符串）指定要用于建议的字段。
        :param suggest_mode:
            (Optional, string) Specifies the suggest mode. Defaults to missing. Available options:
                - always
                - missing
                - popular
            This parameter can only be used when the suggest_field and suggest_text query string parameters are specified.

            （可选，字符串）指定建议模式。默认为 missing。可用选项：
                - always
                - missing
                - popular
            仅当指定了 suggest_field 和 suggest_text 查询字符串参数时，才能使用此参数。
        :param suggest_size:
            (Optional, integer) Number of suggestions to return.
            This parameter can only be used when the suggest_field and suggest_text query string parameters are specified.

            （可选，整数）要返回的建议数。
            仅当指定了 suggest_field 和 suggest_text 查询字符串参数时，才能使用此参数。
        :param suggest_text:
            (Optional, string) The source text for which the suggestions should be returned.
            This parameter can only be used when the suggest_field query string parameter is specified.

            （可选，字符串）应为其返回建议的源文本。
            仅当指定了 suggest_field query string 参数时，才能使用此参数。
        :param terminate_after:
            (Optional, integer) Maximum number of documents to collect for each shard. If a query reaches this limit,
            Elasticsearch terminates the query early. Elasticsearch collects documents before sorting.
            （可选，整数）要为每个分片收集的最大文档数。如果查询达到此限制，Elasticsearch 会提前终止查询。Elasticsearch 在排序之前收集文档。
        :param timeout:
            (Optional, time units) Specifies the period of time to wait for a response from each shard. If no response
            is received before the timeout expires, the request fails and returns an error. Defaults to no timeout.
            （可选，时间单位）指定等待每个分片响应的时间段。如果在超时到期前未收到响应，则请求将失败并返回错误。默认为无超时。
        :param track_scores:
            (Optional, Boolean) If true, calculate and return document scores, even if the scores are not used for
            sorting. Defaults to false.
            （可选，布尔值）如果为 true，则计算并返回文档分数，即使分数不用于排序。默认为 false。
        :param track_total_hits:
            (Optional, integer or Boolean) Number of hits matching the query to count accurately. Defaults to 10000.
            If true, the exact number of hits is returned at the cost of some performance. If false, the response does
            not include the total number of hits matching the query.

            （可选，整数或布尔值）与要准确计数的查询匹配的命中数。默认为 10000。
            如果为 true，则以牺牲一些性能为代价返回确切的点击数。如果为 false，则响应不包括与查询匹配的命中总数。
        :param typed_keys:
            (Optional, Boolean) If true, aggregation and suggester names are be prefixed by their respective types in
            the response. Defaults to true.
            （可选，布尔值）如果为 true，则 aggregation 和 suggester names 在响应中以各自的类型为前缀。默认为 true。
        :param version:
            (Optional, Boolean) If true, returns document version as part of a hit. Defaults to false.
            （可选，布尔值）如果为 true，则返回文档版本作为点击的一部分。默认为 false。
        :param args:
        :param kwargs:
        :return:
        """
        body = self._build()
        resp: ObjectApiResponse = core.es.search(
            index=(
                self._index
                if isinstance(self._index, str)
                else (
                    [_i if isinstance(_i, str) else _i._name for _i in self._index]
                    if isinstance(self._index, Iterable)
                    else self._index._name
                )
            ),
            body=body,

            human=human,
            filter_path=filter_path,
            # flat_settings=flat_settings,
            error_trace=error_trace,
            allow_no_indices=allow_no_indices,
            allow_partial_search_results=allow_partial_search_results,
            analyzer=analyzer.name if isinstance(analyzer, BaseAnalyzer) else analyzer,
            analyze_wildcard=analyze_wildcard,
            batched_reduce_size=batched_reduce_size,
            ccs_minimize_roundtrips=ccs_minimize_roundtrips,
            default_operator=default_operator,
            df=df,
            docvalue_fields=docvalue_fields,
            expand_wildcards=expand_wildcards,
            # from_=from_,
            ignore_throttled=ignore_throttled,
            ignore_unavailable=ignore_unavailable,
            lenient=lenient,
            max_concurrent_shard_requests=max_concurrent_shard_requests,
            pre_filter_shard_size=pre_filter_shard_size,
            preference=preference,
            q=q,
            request_cache=request_cache,
            rest_total_hits_as_int=rest_total_hits_as_int,
            routing=routing,
            scroll=scroll,
            search_type=search_type,
            seq_no_primary_term=seq_no_primary_term,
            # size=size,
            sort=sort,
            source=source,
            source_excludes=source_excludes,
            source_includes=source_includes,
            stats=stats,
            suggest_size=suggest_size,
            stored_fields=stored_fields,
            suggest_field=suggest_field,
            suggest_mode=suggest_mode,
            suggest_text=suggest_text,
            timeout=timeout,
            terminate_after=terminate_after,
            track_scores=track_scores,
            track_total_hits=track_total_hits,
            typed_keys=typed_keys,
            version=version,
            *args, **kwargs
        )
        return resp.body

    def get(self, *args, **kwargs) -> Dict:
        """
        GET请求
        :param args:
        :param kwargs:
        :return:
        """
        raise NotImplementedError(f"还未实现 {self.__class__.__name__}.get() 方法")

